package crp.core.security.domain.services;

import crp.core.security.domain.messages.SecurityMessages;
import crp.core.security.domain.models.CallerPrincipal;
import crp.core.security.domain.models.UserBasicCredentialEntity;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import lombok.extern.slf4j.Slf4j;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.evidence.PasswordGuessEvidence;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.util.ModularCrypt;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import java.security.spec.InvalidKeySpecException;
import java.util.Optional;

@ApplicationScoped
@Slf4j
public class BasicIdentityProvider implements IdentityProvider<UsernamePasswordAuthenticationRequest> {
    private final UserBasicCredentialService userBasicCredentialService;

    @Inject
    public BasicIdentityProvider(UserBasicCredentialService userBasicCredentialService) {
        this.userBasicCredentialService = userBasicCredentialService;
    }

    @Override
    public Class<UsernamePasswordAuthenticationRequest> getRequestType() {
        return UsernamePasswordAuthenticationRequest.class;
    }

    @Override
    @Transactional
    public Uni<SecurityIdentity> authenticate(UsernamePasswordAuthenticationRequest request,
                                              AuthenticationRequestContext context) {
        return Uni.createFrom().item(this.authenticate(request));
    }

    private SecurityIdentity authenticate(UsernamePasswordAuthenticationRequest request) {
        String principal = request.getUsername();
        io.quarkus.security.credential.PasswordCredential password = request.getPassword();
        return Optional.ofNullable(principal)
                .flatMap(userBasicCredentialService::getBy)
                .map(entity -> createSecurityIdentity(entity, password))
                .orElseThrow(SecurityMessages.INSTANCE::usernameNotFound);
    }

    private SecurityIdentity createSecurityIdentity(UserBasicCredentialEntity entity,
                                                    io.quarkus.security.credential.PasswordCredential requestPassword) {
        Password storedPassword = getMcfPassword(entity.getCredential());
        PasswordGuessEvidence sentPasswordEvidence = new PasswordGuessEvidence(requestPassword.getPassword());
        PasswordCredential storedPasswordCredential = new PasswordCredential(storedPassword);
        if (!storedPasswordCredential.verify(sentPasswordEvidence)) {
            throw SecurityMessages.INSTANCE.wrongUsernameOrPassword();
        }
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(CallerPrincipal.fromUser(entity.getUser().getId()));
        builder.addCredential(requestPassword);
        return builder.build();
    }

    protected Password getMcfPassword(String pass) {
        try {
            return ModularCrypt.decode(pass);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }

}
